// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <unordered_map>
#include <vector>

#include "include/v8-context.h"
#include "include/v8-function.h"
#include "include/v8-local-handle.h"
#include "include/v8-object.h"
#include "include/v8-persistent-handle.h"
#include "include/v8-template.h"
#include "include/v8-traced-handle.h"
#include "src/api/api-inl.h"
#include "src/handles/global-handles.h"
#include "src/heap/embedder-tracing.h"
#include "src/heap/heap-inl.h"
#include "src/heap/heap.h"
#include "src/heap/safepoint.h"
#include "src/objects/module.h"
#include "src/objects/objects-inl.h"
#include "src/objects/script.h"
#include "src/objects/shared-function-info.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"

namespace v8 {

namespace internal {
namespace heap {

namespace {

v8::Local<v8::Object> ConstructTraceableJSApiObject(
    v8::Local<v8::Context> context, void* first_field, void* second_field) {
  v8::EscapableHandleScope scope(context->GetIsolate());
  v8::Local<v8::FunctionTemplate> function_t =
      v8::FunctionTemplate::New(context->GetIsolate());
  v8::Local<v8::ObjectTemplate> instance_t = function_t->InstanceTemplate();
  instance_t->SetInternalFieldCount(2);
  v8::Local<v8::Function> function =
      function_t->GetFunction(context).ToLocalChecked();
  v8::Local<v8::Object> instance =
      function->NewInstance(context).ToLocalChecked();
  instance->SetAlignedPointerInInternalField(0, first_field);
  instance->SetAlignedPointerInInternalField(1, second_field);
  CHECK(!instance.IsEmpty());
  i::Handle<i::JSReceiver> js_obj = v8::Utils::OpenHandle(*instance);
  CHECK_EQ(i::JS_API_OBJECT_TYPE, js_obj->map().instance_type());
  return scope.Escape(instance);
}

enum class TracePrologueBehavior { kNoop, kCallV8WriteBarrier };

class TestEmbedderHeapTracer final : public v8::EmbedderHeapTracer {
 public:
  TestEmbedderHeapTracer() = default;
  TestEmbedderHeapTracer(TracePrologueBehavior prologue_behavior,
                         v8::Global<v8::Array> array)
      : prologue_behavior_(prologue_behavior), array_(std::move(array)) {}

  void RegisterV8References(
      const std::vector<std::pair<void*, void*>>& embedder_fields) final {
    registered_from_v8_.insert(registered_from_v8_.end(),
                               embedder_fields.begin(), embedder_fields.end());
  }

  void AddReferenceForTracing(v8::TracedGlobal<v8::Value>* global) {
    to_register_with_v8_.push_back(global);
  }

  void AddReferenceForTracing(v8::TracedReference<v8::Value>* ref) {
    to_register_with_v8_references_.push_back(ref);
  }

  bool AdvanceTracing(double deadline_in_ms) final {
    for (auto global : to_register_with_v8_) {
      RegisterEmbedderReference(global->As<v8::Data>());
    }
    to_register_with_v8_.clear();
    for (auto ref : to_register_with_v8_references_) {
      RegisterEmbedderReference(ref->As<v8::Data>());
    }
    to_register_with_v8_references_.clear();
    return true;
  }

  bool IsTracingDone() final { return to_register_with_v8_.empty(); }

  void TracePrologue(EmbedderHeapTracer::TraceFlags) final {
    if (prologue_behavior_ == TracePrologueBehavior::kCallV8WriteBarrier) {
      auto local = array_.Get(isolate());
      local
          ->Set(local->GetCreationContext().ToLocalChecked(), 0,
                v8::Object::New(isolate()))
          .Check();
    }
  }

  void TraceEpilogue(TraceSummary*) final {}
  void EnterFinalPause(EmbedderStackState) final {}

  bool IsRegisteredFromV8(void* first_field) const {
    for (auto pair : registered_from_v8_) {
      if (pair.first == first_field) return true;
    }
    return false;
  }

  void ConsiderTracedGlobalAsRoot(bool value) {
    consider_traced_global_as_root_ = value;
  }

  bool IsRootForNonTracingGC(const v8::TracedGlobal<v8::Value>& handle) final {
    return consider_traced_global_as_root_;
  }

 private:
  std::vector<std::pair<void*, void*>> registered_from_v8_;
  std::vector<v8::TracedGlobal<v8::Value>*> to_register_with_v8_;
  std::vector<v8::TracedReference<v8::Value>*> to_register_with_v8_references_;
  bool consider_traced_global_as_root_ = true;
  TracePrologueBehavior prologue_behavior_ = TracePrologueBehavior::kNoop;
  v8::Global<v8::Array> array_;
};

}  // namespace

TEST(V8RegisteringEmbedderReference) {
  // Tests that wrappers are properly registered with the embedder heap
  // tracer.
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  v8::HandleScope scope(isolate);
  v8::Local<v8::Context> context = v8::Context::New(isolate);
  v8::Context::Scope context_scope(context);

  void* first_and_second_field = reinterpret_cast<void*>(0x2);
  v8::Local<v8::Object> api_object = ConstructTraceableJSApiObject(
      context, first_and_second_field, first_and_second_field);
  CHECK(!api_object.IsEmpty());
  CcTest::CollectGarbage(i::OLD_SPACE);
  CHECK(tracer.IsRegisteredFromV8(first_and_second_field));
}

TEST(EmbedderRegisteringV8Reference) {
  // Tests that references that are registered by the embedder heap tracer are
  // considered live by V8.
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  v8::HandleScope scope(isolate);
  v8::Local<v8::Context> context = v8::Context::New(isolate);
  v8::Context::Scope context_scope(context);

  v8::TracedGlobal<v8::Value> g;
  {
    v8::HandleScope inner_scope(isolate);
    v8::Local<v8::Value> o =
        v8::Local<v8::Object>::New(isolate, v8::Object::New(isolate));
    g.Reset(isolate, o);
  }
  tracer.AddReferenceForTracing(&g);
  CcTest::CollectGarbage(i::OLD_SPACE);
  CHECK(!g.IsEmpty());
}

namespace {

void ResurrectingFinalizer(
    const v8::WeakCallbackInfo<v8::Global<v8::Object>>& data) {
  data.GetParameter()->ClearWeak();
}

}  // namespace

TEST(TracingInRevivedSubgraph) {
  // Tests that wrappers are traced when they are contained with in a subgraph
  // that is revived by a finalizer.
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  v8::HandleScope scope(isolate);
  v8::Local<v8::Context> context = v8::Context::New(isolate);
  v8::Context::Scope context_scope(context);

  v8::Global<v8::Object> g;
  void* first_and_second_field = reinterpret_cast<void*>(0x4);
  {
    v8::HandleScope inner_scope(isolate);
    v8::Local<v8::Object> api_object = ConstructTraceableJSApiObject(
        context, first_and_second_field, first_and_second_field);
    CHECK(!api_object.IsEmpty());
    v8::Local<v8::Object> o =
        v8::Local<v8::Object>::New(isolate, v8::Object::New(isolate));
    o->Set(context, v8_str("link"), api_object).FromJust();
    g.Reset(isolate, o);
    g.SetWeak(&g, ResurrectingFinalizer, v8::WeakCallbackType::kFinalizer);
  }
  CcTest::CollectGarbage(i::OLD_SPACE);
  CHECK(tracer.IsRegisteredFromV8(first_and_second_field));
}

TEST(TracingInEphemerons) {
  // Tests that wrappers that are part of ephemerons are traced.
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  v8::HandleScope scope(isolate);
  v8::Local<v8::Context> context = v8::Context::New(isolate);
  v8::Context::Scope context_scope(context);

  v8::Local<v8::Object> key =
      v8::Local<v8::Object>::New(isolate, v8::Object::New(isolate));
  void* first_and_second_field = reinterpret_cast<void*>(0x8);
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  Handle<JSWeakMap> weak_map = i_isolate->factory()->NewJSWeakMap();
  {
    v8::HandleScope inner_scope(isolate);
    v8::Local<v8::Object> api_object = ConstructTraceableJSApiObject(
        context, first_and_second_field, first_and_second_field);
    CHECK(!api_object.IsEmpty());
    Handle<JSObject> js_key =
        handle(JSObject::cast(*v8::Utils::OpenHandle(*key)), i_isolate);
    Handle<JSReceiver> js_api_object = v8::Utils::OpenHandle(*api_object);
    int32_t hash = js_key->GetOrCreateHash(i_isolate).value();
    JSWeakCollection::Set(weak_map, js_key, js_api_object, hash);
  }
  CcTest::CollectGarbage(i::OLD_SPACE);
  CHECK(tracer.IsRegisteredFromV8(first_and_second_field));
}

TEST(FinalizeTracingIsNoopWhenNotMarking) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  Isolate* i_isolate = CcTest::i_isolate();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);

  // Finalize a potentially running garbage collection.
  i_isolate->heap()->CollectGarbage(OLD_SPACE,
                                    GarbageCollectionReason::kTesting);
  CHECK(i_isolate->heap()->incremental_marking()->IsStopped());

  int gc_counter = i_isolate->heap()->gc_count();
  tracer.FinalizeTracing();
  CHECK(i_isolate->heap()->incremental_marking()->IsStopped());
  CHECK_EQ(gc_counter, i_isolate->heap()->gc_count());
}

TEST(FinalizeTracingWhenMarking) {
  if (!FLAG_incremental_marking) return;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  Isolate* i_isolate = CcTest::i_isolate();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);

  // Finalize a potentially running garbage collection.
  i_isolate->heap()->CollectGarbage(OLD_SPACE,
                                    GarbageCollectionReason::kTesting);
  if (i_isolate->heap()->mark_compact_collector()->sweeping_in_progress()) {
    i_isolate->heap()->mark_compact_collector()->EnsureSweepingCompleted();
  }
  CHECK(i_isolate->heap()->incremental_marking()->IsStopped());

  i::IncrementalMarking* marking = i_isolate->heap()->incremental_marking();
  {
    SafepointScope scope(i_isolate->heap());
    marking->Start(i::GarbageCollectionReason::kTesting);
  }

  // Sweeping is not runing so we should immediately start marking.
  CHECK(marking->IsMarking());
  tracer.FinalizeTracing();
  CHECK(marking->IsStopped());
}

namespace {

void ConstructJSObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
                       v8::TracedGlobal<v8::Object>* global) {
  v8::HandleScope scope(isolate);
  v8::Local<v8::Object> object(v8::Object::New(isolate));
  CHECK(!object.IsEmpty());
  *global = v8::TracedGlobal<v8::Object>(isolate, object);
  CHECK(!global->IsEmpty());
}

template <typename T>
void ConstructJSApiObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
                          T* global) {
  v8::HandleScope scope(isolate);
  v8::Local<v8::Object> object(
      ConstructTraceableJSApiObject(context, nullptr, nullptr));
  CHECK(!object.IsEmpty());
  *global = T(isolate, object);
  CHECK(!global->IsEmpty());
}

enum class SurvivalMode { kSurvives, kDies };

template <typename ModifierFunction, typename ConstructTracedGlobalFunction>
void TracedGlobalTest(v8::Isolate* isolate,
                      ConstructTracedGlobalFunction construct_function,
                      ModifierFunction modifier_function, void (*gc_function)(),
                      SurvivalMode survives) {
  v8::HandleScope scope(isolate);
  v8::Local<v8::Context> context = v8::Context::New(isolate);
  v8::Context::Scope context_scope(context);

  auto global = std::make_unique<v8::TracedGlobal<v8::Object>>();
  construct_function(isolate, context, global.get());
  CHECK(InCorrectGeneration(isolate, *global));
  modifier_function(*global);
  gc_function();
  CHECK_IMPLIES(survives == SurvivalMode::kSurvives, !global->IsEmpty());
  CHECK_IMPLIES(survives == SurvivalMode::kDies, global->IsEmpty());
}

}  // namespace

TEST(TracedGlobalReset) {
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);

  v8::TracedGlobal<v8::Object> traced;
  ConstructJSObject(isolate, isolate->GetCurrentContext(), &traced);
  CHECK(!traced.IsEmpty());
  traced.Reset();
  CHECK(traced.IsEmpty());
}

TEST(TracedGlobalInStdVector) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);

  std::vector<v8::TracedGlobal<v8::Object>> vec;
  {
    v8::HandleScope new_scope(isolate);
    vec.emplace_back(isolate, v8::Object::New(isolate));
  }
  CHECK(!vec[0].IsEmpty());
  InvokeMarkSweep();
  CHECK(vec[0].IsEmpty());
}

TEST(TracedGlobalCopyWithDestructor) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope outer_scope(isolate);
  i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();

  const size_t initial_count = global_handles->handles_count();
  auto global1 = std::make_unique<v8::TracedGlobal<v8::Object>>();
  {
    v8::HandleScope scope(isolate);
    global1->Reset(isolate, v8::Object::New(isolate));
  }
  auto global2 = std::make_unique<v8::TracedGlobal<v8::Object>>(*global1);
  auto global3 = std::make_unique<v8::TracedGlobal<v8::Object>>();
  *global3 = *global2;
  CHECK_EQ(initial_count + 3, global_handles->handles_count());
  CHECK(!global1->IsEmpty());
  CHECK_EQ(*global1, *global2);
  CHECK_EQ(*global2, *global3);
  {
    v8::HandleScope scope(isolate);
    auto tmp = v8::Local<v8::Object>::New(isolate, *global3);
    CHECK(!tmp.IsEmpty());
    InvokeMarkSweep();
  }
  CHECK_EQ(initial_count + 3, global_handles->handles_count());
  CHECK(!global1->IsEmpty());
  CHECK_EQ(*global1, *global2);
  CHECK_EQ(*global2, *global3);
  InvokeMarkSweep();
  CHECK_EQ(initial_count, global_handles->handles_count());
  CHECK(global1->IsEmpty());
  CHECK_EQ(*global1, *global2);
  CHECK_EQ(*global2, *global3);
}

TEST(TracedGlobalCopyNoDestructor) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope outer_scope(isolate);
  i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();

  const size_t initial_count = global_handles->handles_count();
  auto global1 = std::make_unique<v8::TracedReference<v8::Value>>();
  {
    v8::HandleScope scope(isolate);
    global1->Reset(isolate, v8::Object::New(isolate));
  }
  auto global2 = std::make_unique<v8::TracedReference<v8::Value>>(*global1);
  auto global3 = std::make_unique<v8::TracedReference<v8::Value>>();
  *global3 = *global2;
  CHECK_EQ(initial_count + 3, global_handles->handles_count());
  CHECK(!global1->IsEmpty());
  CHECK_EQ(*global1, *global2);
  CHECK_EQ(*global2, *global3);
  {
    v8::HandleScope scope(isolate);
    auto tmp = v8::Local<v8::Value>::New(isolate, *global3);
    CHECK(!tmp.IsEmpty());
    InvokeMarkSweep();
  }
  CHECK_EQ(initial_count + 3, global_handles->handles_count());
  CHECK(!global1->IsEmpty());
  CHECK_EQ(*global1, *global2);
  CHECK_EQ(*global2, *global3);
  InvokeMarkSweep();
  CHECK_EQ(initial_count, global_handles->handles_count());
}

TEST(TracedGlobalInStdUnorderedMap) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);

  std::unordered_map<int, v8::TracedGlobal<v8::Object>> map;
  {
    v8::HandleScope new_scope(isolate);
    map.emplace(std::piecewise_construct, std::forward_as_tuple(1),
                std::forward_as_tuple(isolate, v8::Object::New(isolate)));
  }
  CHECK(!map[1].IsEmpty());
  InvokeMarkSweep();
  CHECK(map[1].IsEmpty());
}

TEST(TracedGlobalToUnmodifiedJSObjectDiesOnMarkSweep) {
  // When stressing incremental marking, a write barrier may keep the object
  // alive.
  if (FLAG_stress_incremental_marking) return;

  CcTest::InitializeVM();
  TracedGlobalTest(
      CcTest::isolate(), ConstructJSObject,
      [](const TracedGlobal<v8::Object>& global) {}, [] { InvokeMarkSweep(); },
      SurvivalMode::kDies);
}

TEST(TracedGlobalToUnmodifiedJSObjectSurvivesMarkSweepWhenHeldAliveOtherwise) {
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::Global<v8::Object> strong_global;
  TracedGlobalTest(
      CcTest::isolate(), ConstructJSObject,
      [isolate, &strong_global](const TracedGlobal<v8::Object>& global) {
        v8::HandleScope scope(isolate);
        strong_global = v8::Global<v8::Object>(isolate, global.Get(isolate));
      },
      []() { InvokeMarkSweep(); }, SurvivalMode::kSurvives);
}

TEST(TracedGlobalToUnmodifiedJSObjectSurvivesScavenge) {
  if (FLAG_single_generation) return;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TracedGlobalTest(
      CcTest::isolate(), ConstructJSObject,
      [](const TracedGlobal<v8::Object>& global) {}, []() { InvokeScavenge(); },
      SurvivalMode::kSurvives);
}

TEST(TracedGlobalToUnmodifiedJSObjectSurvivesScavengeWhenExcludedFromRoots) {
  if (FLAG_single_generation) return;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  tracer.ConsiderTracedGlobalAsRoot(false);
  TracedGlobalTest(
      CcTest::isolate(), ConstructJSObject,
      [](const TracedGlobal<v8::Object>& global) {}, []() { InvokeScavenge(); },
      SurvivalMode::kSurvives);
}

TEST(TracedGlobalToUnmodifiedJSApiObjectSurvivesScavengePerDefault) {
  if (FLAG_single_generation) return;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  tracer.ConsiderTracedGlobalAsRoot(true);
  TracedGlobalTest(
      CcTest::isolate(), ConstructJSApiObject<TracedGlobal<v8::Object>>,
      [](const TracedGlobal<v8::Object>& global) {}, []() { InvokeScavenge(); },
      SurvivalMode::kSurvives);
}

TEST(TracedGlobalToUnmodifiedJSApiObjectDiesOnScavengeWhenExcludedFromRoots) {
  if (FLAG_single_generation) return;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  tracer.ConsiderTracedGlobalAsRoot(false);
  TracedGlobalTest(
      CcTest::isolate(), ConstructJSApiObject<TracedGlobal<v8::Object>>,
      [](const TracedGlobal<v8::Object>& global) {}, []() { InvokeScavenge(); },
      SurvivalMode::kDies);
}

TEST(TracedGlobalWrapperClassId) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);

  v8::TracedGlobal<v8::Object> traced;
  ConstructJSObject(isolate, isolate->GetCurrentContext(), &traced);
  CHECK_EQ(0, traced.WrapperClassId());
  traced.SetWrapperClassId(17);
  CHECK_EQ(17, traced.WrapperClassId());
}

TEST(TracedReferenceHandlesMarking) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  auto live = std::make_unique<v8::TracedReference<v8::Value>>();
  auto dead = std::make_unique<v8::TracedReference<v8::Value>>();
  live->Reset(isolate, v8::Undefined(isolate));
  dead->Reset(isolate, v8::Undefined(isolate));
  i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
  {
    TestEmbedderHeapTracer tracer;
    heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
    tracer.AddReferenceForTracing(live.get());
    const size_t initial_count = global_handles->handles_count();
    InvokeMarkSweep();
    const size_t final_count = global_handles->handles_count();
    // Handles are black allocated, so the first GC does not collect them.
    CHECK_EQ(initial_count, final_count);
  }

  {
    TestEmbedderHeapTracer tracer;
    heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
    tracer.AddReferenceForTracing(live.get());
    const size_t initial_count = global_handles->handles_count();
    InvokeMarkSweep();
    const size_t final_count = global_handles->handles_count();
    CHECK_EQ(initial_count, final_count + 1);
  }
}

TEST(TracedReferenceHandlesDoNotLeak) {
  // TracedReference handles are not cleared by the destructor of the embedder
  // object. To avoid leaks we need to mark these handles during GC.
  // This test checks that unmarked handles do not leak.
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  auto ref = std::make_unique<v8::TracedReference<v8::Value>>();
  ref->Reset(isolate, v8::Undefined(isolate));
  i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
  const size_t initial_count = global_handles->handles_count();
  // We need two GCs because handles are black allocated.
  InvokeMarkSweep();
  InvokeMarkSweep();
  const size_t final_count = global_handles->handles_count();
  CHECK_EQ(initial_count, final_count + 1);
}

TEST(TracedGlobalHandlesAreRetained) {
  // TracedGlobal handles are cleared by the destructor of the embedder object.
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::TracedGlobal<v8::Value> global;
  global.Reset(isolate, v8::Undefined(isolate));
  i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
  const size_t initial_count = global_handles->handles_count();
  // We need two GCs because handles are black allocated.
  InvokeMarkSweep();
  InvokeMarkSweep();
  const size_t final_count = global_handles->handles_count();
  CHECK_EQ(initial_count, final_count);
}

namespace {

class TracedGlobalVisitor final
    : public v8::EmbedderHeapTracer::TracedGlobalHandleVisitor {
 public:
  ~TracedGlobalVisitor() override = default;
  void VisitTracedGlobalHandle(const TracedGlobal<Value>& value) final {
    if (value.WrapperClassId() == 57) {
      count_++;
    }
  }

  size_t count() const { return count_; }

 private:
  size_t count_ = 0;
};

}  // namespace

TEST(TracedGlobalIteration) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);

  auto traced = std::make_unique<v8::TracedGlobal<v8::Object>>();
  ConstructJSObject(isolate, isolate->GetCurrentContext(), traced.get());
  CHECK(!traced->IsEmpty());
  traced->SetWrapperClassId(57);
  TracedGlobalVisitor visitor;
  {
    v8::HandleScope new_scope(isolate);
    tracer.IterateTracedGlobalHandles(&visitor);
  }
  CHECK_EQ(1, visitor.count());
}

namespace {

void FinalizationCallback(const WeakCallbackInfo<void>& data) {
  v8::TracedGlobal<v8::Object>* traced =
      reinterpret_cast<v8::TracedGlobal<v8::Object>*>(data.GetParameter());
  CHECK_EQ(reinterpret_cast<void*>(0x4), data.GetInternalField(0));
  CHECK_EQ(reinterpret_cast<void*>(0x8), data.GetInternalField(1));
  traced->Reset();
}

}  // namespace

TEST(TracedGlobalSetFinalizationCallbackScavenge) {
  if (FLAG_single_generation) return;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  TestEmbedderHeapTracer tracer;
  tracer.ConsiderTracedGlobalAsRoot(false);
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);

  auto traced = std::make_unique<v8::TracedGlobal<v8::Object>>();
  ConstructJSApiObject(isolate, isolate->GetCurrentContext(), traced.get());
  CHECK(!traced->IsEmpty());
  {
    v8::HandleScope new_scope(isolate);
    auto local = traced->Get(isolate);
    local->SetAlignedPointerInInternalField(0, reinterpret_cast<void*>(0x4));
    local->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(0x8));
  }
  traced->SetFinalizationCallback(traced.get(), FinalizationCallback);
  heap::InvokeScavenge();
  CHECK(traced->IsEmpty());
}

TEST(TracedGlobalSetFinalizationCallbackMarkSweep) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);

  auto traced = std::make_unique<v8::TracedGlobal<v8::Object>>();
  ConstructJSApiObject(isolate, isolate->GetCurrentContext(), traced.get());
  CHECK(!traced->IsEmpty());
  {
    v8::HandleScope new_scope(isolate);
    auto local = traced->Get(isolate);
    local->SetAlignedPointerInInternalField(0, reinterpret_cast<void*>(0x4));
    local->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(0x8));
  }
  traced->SetFinalizationCallback(traced.get(), FinalizationCallback);
  heap::InvokeMarkSweep();
  CHECK(traced->IsEmpty());
}

TEST(TracePrologueCallingIntoV8WriteBarrier) {
  // Regression test: https://crbug.com/940003
  if (!FLAG_incremental_marking) return;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Global<v8::Array> global;
  {
    v8::HandleScope new_scope(isolate);
    auto local = v8::Array::New(isolate, 10);
    global.Reset(isolate, local);
  }
  TestEmbedderHeapTracer tracer(TracePrologueBehavior::kCallV8WriteBarrier,
                                std::move(global));
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  SimulateIncrementalMarking(CcTest::i_isolate()->heap());
  // Finish GC to avoid removing the tracer while GC is running which may end up
  // in an infinite loop because of unprocessed objects.
  heap::InvokeMarkSweep();
}

TEST(TracedGlobalWithDestructor) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();

  const size_t initial_count = global_handles->handles_count();
  auto* traced = new v8::TracedGlobal<v8::Object>();
  {
    v8::HandleScope new_scope(isolate);
    v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
        isolate->GetCurrentContext(), nullptr, nullptr));
    CHECK(traced->IsEmpty());
    *traced = v8::TracedGlobal<v8::Object>(isolate, object);
    CHECK(!traced->IsEmpty());
    CHECK_EQ(initial_count + 1, global_handles->handles_count());
  }
  delete traced;
  CHECK_EQ(initial_count, global_handles->handles_count());
  // GC should not need to clear the handle.
  heap::InvokeMarkSweep();
  CHECK_EQ(initial_count, global_handles->handles_count());
}

TEST(TracedGlobalNoDestructor) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();

  const size_t initial_count = global_handles->handles_count();
  char* memory = new char[sizeof(v8::TracedReference<v8::Value>)];
  auto* traced = new (memory) v8::TracedReference<v8::Value>();
  {
    v8::HandleScope new_scope(isolate);
    v8::Local<v8::Value> object(ConstructTraceableJSApiObject(
        isolate->GetCurrentContext(), nullptr, nullptr));
    CHECK(traced->IsEmpty());
    *traced = v8::TracedReference<v8::Value>(isolate, object);
    CHECK(!traced->IsEmpty());
    CHECK_EQ(initial_count + 1, global_handles->handles_count());
  }
  traced->~TracedReference<v8::Value>();
  CHECK_EQ(initial_count + 1, global_handles->handles_count());
  // GC should clear the handle.
  heap::InvokeMarkSweep();
  CHECK_EQ(initial_count, global_handles->handles_count());
  delete[] memory;
}

namespace {

class EmptyEmbedderHeapTracer : public v8::EmbedderHeapTracer {
 public:
  void RegisterV8References(
      const std::vector<std::pair<void*, void*>>& embedder_fields) final {}

  bool AdvanceTracing(double deadline_in_ms) final { return true; }
  bool IsTracingDone() final { return true; }
  void TracePrologue(EmbedderHeapTracer::TraceFlags) final {}
  void TraceEpilogue(TraceSummary*) final {}
  void EnterFinalPause(EmbedderStackState) final {}
};

// EmbedderHeapTracer that can optimize Scavenger handling when used with
// TraceGlobal handles that have destructors.
class EmbedderHeapTracerDestructorNonTracingClearing final
    : public EmptyEmbedderHeapTracer {
 public:
  explicit EmbedderHeapTracerDestructorNonTracingClearing(
      uint16_t class_id_to_optimize)
      : class_id_to_optimize_(class_id_to_optimize) {}

  bool IsRootForNonTracingGC(const v8::TracedGlobal<v8::Value>& handle) final {
    return handle.WrapperClassId() != class_id_to_optimize_;
  }

 private:
  uint16_t class_id_to_optimize_;
};

// EmbedderHeapTracer that can optimize Scavenger handling when used with
// TraceGlobal handles without destructors.
class EmbedderHeapTracerNoDestructorNonTracingClearing final
    : public EmptyEmbedderHeapTracer {
 public:
  explicit EmbedderHeapTracerNoDestructorNonTracingClearing(
      uint16_t class_id_to_optimize)
      : class_id_to_optimize_(class_id_to_optimize) {}

  bool IsRootForNonTracingGC(
      const v8::TracedReference<v8::Value>& handle) final {
    return handle.WrapperClassId() != class_id_to_optimize_;
  }

  void ResetHandleInNonTracingGC(
      const v8::TracedReference<v8::Value>& handle) final {
    if (handle.WrapperClassId() != class_id_to_optimize_) return;

    // Convention (for test): Objects that are optimized have their first field
    // set as a back pointer.
    BasicTracedReference<v8::Value>* original_handle =
        reinterpret_cast<BasicTracedReference<v8::Value>*>(
            v8::Object::GetAlignedPointerFromInternalField(
                handle.As<v8::Object>(), 0));
    original_handle->Reset();
  }

 private:
  uint16_t class_id_to_optimize_;
};

template <typename T>
void SetupOptimizedAndNonOptimizedHandle(v8::Isolate* isolate,
                                         uint16_t optimized_class_id,
                                         T* optimized_handle,
                                         T* non_optimized_handle) {
  v8::HandleScope scope(isolate);

  v8::Local<v8::Object> optimized_object(ConstructTraceableJSApiObject(
      isolate->GetCurrentContext(), optimized_handle, nullptr));
  CHECK(optimized_handle->IsEmpty());
  *optimized_handle = T(isolate, optimized_object);
  CHECK(!optimized_handle->IsEmpty());
  optimized_handle->SetWrapperClassId(optimized_class_id);

  v8::Local<v8::Object> non_optimized_object(ConstructTraceableJSApiObject(
      isolate->GetCurrentContext(), nullptr, nullptr));
  CHECK(non_optimized_handle->IsEmpty());
  *non_optimized_handle = T(isolate, non_optimized_object);
  CHECK(!non_optimized_handle->IsEmpty());
}

}  // namespace

TEST(TracedGlobalDestructorReclaimedOnScavenge) {
  if (FLAG_single_generation) return;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  constexpr uint16_t kClassIdToOptimize = 17;
  EmbedderHeapTracerDestructorNonTracingClearing tracer(kClassIdToOptimize);
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();

  const size_t initial_count = global_handles->handles_count();
  auto* optimized_handle = new v8::TracedGlobal<v8::Object>();
  auto* non_optimized_handle = new v8::TracedGlobal<v8::Object>();
  SetupOptimizedAndNonOptimizedHandle(isolate, kClassIdToOptimize,
                                      optimized_handle, non_optimized_handle);
  CHECK_EQ(initial_count + 2, global_handles->handles_count());
  heap::InvokeScavenge();
  CHECK_EQ(initial_count + 1, global_handles->handles_count());
  CHECK(optimized_handle->IsEmpty());
  delete optimized_handle;
  CHECK(!non_optimized_handle->IsEmpty());
  delete non_optimized_handle;
  CHECK_EQ(initial_count, global_handles->handles_count());
}

TEST(TracedGlobalNoDestructorReclaimedOnScavenge) {
  if (FLAG_single_generation) return;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  constexpr uint16_t kClassIdToOptimize = 23;
  EmbedderHeapTracerNoDestructorNonTracingClearing tracer(kClassIdToOptimize);
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
  i::GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();

  const size_t initial_count = global_handles->handles_count();
  auto* optimized_handle = new v8::TracedReference<v8::Value>();
  auto* non_optimized_handle = new v8::TracedReference<v8::Value>();
  SetupOptimizedAndNonOptimizedHandle(isolate, kClassIdToOptimize,
                                      optimized_handle, non_optimized_handle);
  CHECK_EQ(initial_count + 2, global_handles->handles_count());
  heap::InvokeScavenge();
  CHECK_EQ(initial_count + 1, global_handles->handles_count());
  CHECK(optimized_handle->IsEmpty());
  delete optimized_handle;
  CHECK(!non_optimized_handle->IsEmpty());
  non_optimized_handle->Reset();
  delete non_optimized_handle;
  CHECK_EQ(initial_count, global_handles->handles_count());
}

namespace {

template <typename T>
V8_NOINLINE void OnStackTest(TestEmbedderHeapTracer* tracer) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::Global<v8::Object> observer;
  T stack_ref;
  {
    v8::HandleScope scope(isolate);
    v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
        isolate->GetCurrentContext(), nullptr, nullptr));
    stack_ref.Reset(isolate, object);
    observer.Reset(isolate, object);
    observer.SetWeak();
  }
  CHECK(!observer.IsEmpty());
  heap::InvokeMarkSweep();
  CHECK(!observer.IsEmpty());
}

V8_NOINLINE void CreateTracedReferenceInDeepStack(
    v8::Isolate* isolate, v8::Global<v8::Object>* observer) {
  v8::TracedReference<v8::Value> stack_ref;
  v8::HandleScope scope(isolate);
  v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
      isolate->GetCurrentContext(), nullptr, nullptr));
  stack_ref.Reset(isolate, object);
  observer->Reset(isolate, object);
  observer->SetWeak();
}

V8_NOINLINE void TracedReferenceNotifyEmptyStackTest(
    TestEmbedderHeapTracer* tracer) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::Global<v8::Object> observer;
  CreateTracedReferenceInDeepStack(isolate, &observer);
  CHECK(!observer.IsEmpty());
  reinterpret_cast<i::Isolate*>(isolate)
      ->heap()
      ->local_embedder_heap_tracer()
      ->NotifyEmptyEmbedderStack();
  heap::InvokeMarkSweep();
  CHECK(observer.IsEmpty());
}

enum class Operation {
  kCopy,
  kMove,
};

template <typename T>
void PerformOperation(Operation op, T* lhs, T* rhs) {
  switch (op) {
    case Operation::kMove:
      *lhs = std::move(*rhs);
      break;
    case Operation::kCopy:
      *lhs = *rhs;
      rhs->Reset();
      break;
  }
}

enum class TargetHandling {
  kNonInitialized,
  kInitializedYoungGen,
  kInitializedOldGen
};

template <typename T>
V8_NOINLINE void StackToHeapTest(TestEmbedderHeapTracer* tracer, Operation op,
                                 TargetHandling target_handling) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::Global<v8::Object> observer;
  T stack_handle;
  T* heap_handle = new T();
  if (target_handling != TargetHandling::kNonInitialized) {
    v8::HandleScope scope(isolate);
    v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
        isolate->GetCurrentContext(), nullptr, nullptr));
    CHECK(InCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
    if (!FLAG_single_generation &&
        target_handling == TargetHandling::kInitializedOldGen) {
      heap::InvokeScavenge();
      heap::InvokeScavenge();
      CHECK(!i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
    }
    heap_handle->Reset(isolate, to_object);
  }
  {
    v8::HandleScope scope(isolate);
    v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
        isolate->GetCurrentContext(), nullptr, nullptr));
    stack_handle.Reset(isolate, object);
    observer.Reset(isolate, object);
    observer.SetWeak();
  }
  CHECK(!observer.IsEmpty());
  tracer->AddReferenceForTracing(heap_handle);
  heap::InvokeMarkSweep();
  CHECK(!observer.IsEmpty());
  tracer->AddReferenceForTracing(heap_handle);
  PerformOperation(op, heap_handle, &stack_handle);
  heap::InvokeMarkSweep();
  CHECK(!observer.IsEmpty());
  heap::InvokeMarkSweep();
  CHECK(observer.IsEmpty());
  delete heap_handle;
}

template <typename T>
V8_NOINLINE void HeapToStackTest(TestEmbedderHeapTracer* tracer, Operation op,
                                 TargetHandling target_handling) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::Global<v8::Object> observer;
  T stack_handle;
  T* heap_handle = new T();
  if (target_handling != TargetHandling::kNonInitialized) {
    v8::HandleScope scope(isolate);
    v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
        isolate->GetCurrentContext(), nullptr, nullptr));
    CHECK(InCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
    if (!FLAG_single_generation &&
        target_handling == TargetHandling::kInitializedOldGen) {
      heap::InvokeScavenge();
      heap::InvokeScavenge();
      CHECK(!i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
    }
    stack_handle.Reset(isolate, to_object);
  }
  {
    v8::HandleScope scope(isolate);
    v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
        isolate->GetCurrentContext(), nullptr, nullptr));
    heap_handle->Reset(isolate, object);
    observer.Reset(isolate, object);
    observer.SetWeak();
  }
  CHECK(!observer.IsEmpty());
  tracer->AddReferenceForTracing(heap_handle);
  heap::InvokeMarkSweep();
  CHECK(!observer.IsEmpty());
  PerformOperation(op, &stack_handle, heap_handle);
  heap::InvokeMarkSweep();
  CHECK(!observer.IsEmpty());
  stack_handle.Reset();
  heap::InvokeMarkSweep();
  CHECK(observer.IsEmpty());
  delete heap_handle;
}

template <typename T>
V8_NOINLINE void StackToStackTest(TestEmbedderHeapTracer* tracer, Operation op,
                                  TargetHandling target_handling) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::Global<v8::Object> observer;
  T stack_handle1;
  T stack_handle2;
  if (target_handling != TargetHandling::kNonInitialized) {
    v8::HandleScope scope(isolate);
    v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
        isolate->GetCurrentContext(), nullptr, nullptr));
    CHECK(InCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
    if (!FLAG_single_generation &&
        target_handling == TargetHandling::kInitializedOldGen) {
      heap::InvokeScavenge();
      heap::InvokeScavenge();
      CHECK(!i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
    }
    stack_handle2.Reset(isolate, to_object);
  }
  {
    v8::HandleScope scope(isolate);
    v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
        isolate->GetCurrentContext(), nullptr, nullptr));
    stack_handle1.Reset(isolate, object);
    observer.Reset(isolate, object);
    observer.SetWeak();
  }
  CHECK(!observer.IsEmpty());
  heap::InvokeMarkSweep();
  CHECK(!observer.IsEmpty());
  PerformOperation(op, &stack_handle2, &stack_handle1);
  heap::InvokeMarkSweep();
  CHECK(!observer.IsEmpty());
  stack_handle2.Reset();
  heap::InvokeMarkSweep();
  CHECK(observer.IsEmpty());
}

template <typename T>
V8_NOINLINE void TracedReferenceCleanedTest(TestEmbedderHeapTracer* tracer) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
      isolate->GetCurrentContext(), nullptr, nullptr));
  const size_t before =
      CcTest::i_isolate()->global_handles()->NumberOfOnStackHandlesForTesting();
  for (int i = 0; i < 100; i++) {
    T stack_handle;
    stack_handle.Reset(isolate, object);
  }
  CHECK_EQ(before + 1, CcTest::i_isolate()
                           ->global_handles()
                           ->NumberOfOnStackHandlesForTesting());
}

V8_NOINLINE void TracedGlobalDestructorTest(TestEmbedderHeapTracer* tracer) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::Global<v8::Object> observer;
  {
    v8::TracedGlobal<v8::Value> stack_handle;
    {
      v8::HandleScope scope(isolate);
      v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
          isolate->GetCurrentContext(), nullptr, nullptr));
      stack_handle.Reset(isolate, object);
      observer.Reset(isolate, object);
      observer.SetWeak();
    }
    CHECK(!observer.IsEmpty());
    heap::InvokeMarkSweep();
    CHECK(!observer.IsEmpty());
  }
  heap::InvokeMarkSweep();
  CHECK(observer.IsEmpty());
}

}  // namespace

TEST(TracedReferenceOnStack) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
                                                      &tracer);
  tracer.SetStackStart(&manual_gc);
  OnStackTest<v8::TracedReference<v8::Value>>(&tracer);
}

TEST(TracedGlobalOnStack) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
                                                      &tracer);
  tracer.SetStackStart(&manual_gc);
  OnStackTest<v8::TracedGlobal<v8::Value>>(&tracer);
}

TEST(TracedReferenceCleaned) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
                                                      &tracer);
  tracer.SetStackStart(&manual_gc);
  TracedReferenceCleanedTest<v8::TracedReference<v8::Value>>(&tracer);
}

TEST(TracedGlobalCleaned) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
                                                      &tracer);
  tracer.SetStackStart(&manual_gc);
  TracedReferenceCleanedTest<v8::TracedGlobal<v8::Value>>(&tracer);
}

TEST(TracedReferenceMove) {
  using ReferenceType = v8::TracedReference<v8::Value>;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
                                                      &tracer);
  tracer.SetStackStart(&manual_gc);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kNonInitialized);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kInitializedYoungGen);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kInitializedOldGen);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kNonInitialized);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kInitializedYoungGen);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kInitializedOldGen);
  StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                  TargetHandling::kNonInitialized);
  StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                  TargetHandling::kInitializedYoungGen);
  StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                  TargetHandling::kInitializedOldGen);
}

TEST(TracedReferenceCopy) {
  using ReferenceType = v8::TracedReference<v8::Value>;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
                                                      &tracer);
  tracer.SetStackStart(&manual_gc);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kNonInitialized);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kInitializedYoungGen);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kInitializedOldGen);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kNonInitialized);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kInitializedYoungGen);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kInitializedOldGen);
  StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                  TargetHandling::kNonInitialized);
  StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                  TargetHandling::kInitializedYoungGen);
  StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                  TargetHandling::kInitializedOldGen);
}

TEST(TracedGlobalMove) {
  using ReferenceType = v8::TracedGlobal<v8::Value>;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
                                                      &tracer);
  tracer.SetStackStart(&manual_gc);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kNonInitialized);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kInitializedYoungGen);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kInitializedOldGen);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kNonInitialized);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kInitializedYoungGen);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                 TargetHandling::kInitializedOldGen);
  StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                  TargetHandling::kNonInitialized);
  StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                  TargetHandling::kInitializedYoungGen);
  StackToStackTest<ReferenceType>(&tracer, Operation::kMove,
                                  TargetHandling::kInitializedOldGen);
}

TEST(TracedGlobalCopy) {
  using ReferenceType = v8::TracedGlobal<v8::Value>;
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
                                                      &tracer);
  tracer.SetStackStart(&manual_gc);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kNonInitialized);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kInitializedYoungGen);
  StackToHeapTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kInitializedOldGen);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kNonInitialized);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kInitializedYoungGen);
  HeapToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                 TargetHandling::kInitializedOldGen);
  StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                  TargetHandling::kNonInitialized);
  StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                  TargetHandling::kInitializedYoungGen);
  StackToStackTest<ReferenceType>(&tracer, Operation::kCopy,
                                  TargetHandling::kInitializedOldGen);
}

TEST(TracedGlobalDestructor) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
                                                      &tracer);
  tracer.SetStackStart(&manual_gc);
  TracedGlobalDestructorTest(&tracer);
}

TEST(NotifyEmptyStack) {
  ManualGCScope manual_gc;
  CcTest::InitializeVM();
  TestEmbedderHeapTracer tracer;
  heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
                                                      &tracer);
  tracer.SetStackStart(&manual_gc);
  TracedReferenceNotifyEmptyStackTest(&tracer);
}

}  // namespace heap
}  // namespace internal
}  // namespace v8
